package com.wdd.myplatform.service.impl;

import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.google.common.collect.Maps;
import com.wdd.myplatform.common.Result;
import com.wdd.myplatform.entity.wx.NotifyBody;
import com.wdd.myplatform.entity.wx.WeChatPayNotify;
import com.wdd.myplatform.entity.wx.WeChatPayOrder;
import com.wdd.myplatform.entity.wx.WeChatWebUserInfo;
import com.wdd.myplatform.service.IWeChatService;
import com.wdd.myplatform.utils.Constants;
import com.wdd.myplatform.utils.Md5Utils;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author Administrator
 */
@Service
@Slf4j
public class WeChatServiceImpl implements IWeChatService {

    @Value("${wechat.appid}")
    private String appId;

    @Value("${wechat.secret}")
    private String secret;
    // 商户号
    @Value("${wechat.pay.mchid}")
    private String mchId;
    // 商户API证书序列号
    @Value("${wechat.pay.mchSerialNo}")
    private String mchSerialNo;
    // 商户API证书序列号
    @Value("${wechat.pay.apiV3Key}")
    private String apiV3Key;

    @Value("${wechat.msg.passRecordNoticeTemplateId}")
    private String passRecordNoticeTemplateId;

    @Value("${wechat.msg.passRecordPageUrl}")
    private String passRecordPageUrl;

    private CloseableHttpClient httpClient;

    private final String WECHAT_WEB_ACCESSTOKEN = "wechat-web-accesstoken:";

    private final String WECHAT_WEB_USERINFO = "wechat-web-userinfo:";

    private final String MD5ENCODE = "wechat-pay-notify:md5Encode:";

//    @Autowired
//    private IWeChatPayOrderService iWeChatPayOrderService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

//    @Autowired
//    private IWeChatPayNotifyService iWeChatPayNotifyService;
    /**
     * 网页授权根据code获取用户信息
     * @param code
     * @return
     */
    @Override
    public Result<?> getWeChatUserInfoByCode(String code) {
        JSONObject accessTokenObj = getAccessTokenByCode(code);
        if(null == accessTokenObj){
            return Result.error("获取网页授权access_token失败");
        }
        WeChatWebUserInfo weChatWebUserInfo = getWeChatInfoByAccessToken(accessTokenObj);
        return Result.OK(weChatWebUserInfo);
    }

    /**
     * 根据openId获取网页授权用户信息
     * @param openId
     * @return
     */
    @Override
    public Result<?> getWeChatUserInfoByOpenId(String openId) {
        String userInfoStr = stringRedisTemplate.opsForValue().get(WECHAT_WEB_USERINFO + openId);
        if(StringUtils.isBlank(userInfoStr)){
            return Result.error("用户信息过期，请重新授权获取用户信息");
        }
        WeChatWebUserInfo weChatWebUserInfo = JSON.parseObject(userInfoStr, WeChatWebUserInfo.class);
        return Result.OK(weChatWebUserInfo);
    }

    /**
     * 获取普通accessToken
     * @return
     */
    @Override
    public Result<?> getNormalAccessToken() {
        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
        url = String.format(url,appId,secret);
        String accessTokenResult = HttpUtil.get(url);
        if(StringUtils.isBlank(accessTokenResult)){
            return Result.error("获取accessToken失败");
        }
        JSONObject jsonObject = JSON.parseObject(accessTokenResult);
        String accessToken = jsonObject.getString("access_token");
        if(StringUtils.isBlank(accessToken)){
            String errCode = jsonObject.getString("errcode");
            String errMsg = jsonObject.getString("errmsg");
            log.error("获取普通accessToken失败,错误码为"+errCode+",错误信息"+errMsg);
            return Result.error("获取普通accessToken失败,错误码为"+errCode+",错误信息"+errMsg);
        }
        return Result.OK(accessToken);
    }

    /**
     * 公众号创建菜单
     * @param jsonObject
     * @return
     */
    @Override
    public Result<?> createMenu(JSONObject jsonObject) {
        String url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=%s";
        Result<?> accessTokenResult = getNormalAccessToken();
        boolean success = accessTokenResult.isSuccess();
        if(!success){
            return accessTokenResult;
        }
        String accessToken = accessTokenResult.getResult().toString();
        url = String.format(url,accessToken);
        try {
            String postResult = HttpUtil.post(url, jsonObject.toString());
            if(StringUtils.isBlank(postResult)){
                return Result.error("公众号创建菜单失败");
            }
            JSONObject parseObject = JSON.parseObject(postResult);
            if(parseObject.getInteger("errcode").equals(0)){
                return Result.OK();
            }
        }catch (Exception e){
            log.error("公众号创建菜单失败",e);
            return Result.error("公众号创建菜单失败");
        }
        return Result.OK();
    }

    /**
     * 调用微信生成预支付交易单
     * @param jsonObject
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<?> createOrder(JSONObject jsonObject) throws Exception{
        //金额
        int total = jsonObject.getInteger("total");
        String openid = jsonObject.getString("openid");

        WeChatPayOrder weChatPayOrder = new WeChatPayOrder();
        // TODO: 2022/5/20  向预订单表中插入数据
        //向预订单表中插入数据
        /*weChatPayOrder.setOpenId(openid);
        weChatPayOrder.setOrderStatus(0);
        weChatPayOrder.setTotal(total);
        Date date = new Date();
        weChatPayOrder.setCreateTime(date);
        weChatPayOrder.setUpdateTime(date);
        iWeChatPayOrderService.saveOrUpdate(weChatPayOrder);*/
        String outTradeNo = weChatPayOrder.getId();
        //初始化httpClient
        CloseableHttpClient httpClient = setupHttpClient();
        //请求URL
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
        // 请求body参数
        JSONObject reqDataObj = new JSONObject();
        //应用ID
        reqDataObj.put("appid",appId);
        //直连商户号
        reqDataObj.put("mchid",mchId);
        //商品描述
        reqDataObj.put("description","学生消息推送付费");
        //商户订单号
        reqDataObj.put("out_trade_no", outTradeNo);
        //异步接收微信支付结果通知的回调地址
        reqDataObj.put("notify_url", "url");

        //订单金额
        JSONObject amountObj = new JSONObject();
        //订单总金额，单位为分。
        amountObj.put("total",total);
        amountObj.put("currency","CNY");
        reqDataObj.put("amount",amountObj);

        //支付者信息
        JSONObject payerObj = new JSONObject();
        //用户标识
        payerObj.put("openid",openid);
        reqDataObj.put("payer",payerObj);
        String reqdata = reqDataObj.toJSONString();
        /*String reqdata = "{"
                + "\"amount\": {"
                + "\"total\": 100,"
                + "\"currency\": \"CNY\""
                + "},"
                + "\"mchid\": \"1900006891\","
                + "\"description\": \"Image形象店-深圳腾大-QQ公仔\","
                + "\"notify_url\": \"https://www.weixin.qq.com/wxpay/pay.php\","
                + "\"payer\": {"
                + "\"openid\": \"o4GgauE1lgaPsLabrYvqhVg7O8yA\"" + "},"
                + "\"out_trade_no\": \"1217752501201407033233388881\","
                + "\"goods_tag\": \"WXG\","
                + "\"appid\": \"wxdace645e0bc2c424\"" + "}";*/
        StringEntity entity = new StringEntity(reqdata,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        //完成签名并执行请求
        CloseableHttpResponse response = httpClient.execute(httpPost);
        String payResult = EntityUtils.toString(response.getEntity());
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("success,return body = " + payResult);
                weChatPayOrder.setPayResult(payResult);
            } else if (statusCode == 204) {
                log.info("success");
                weChatPayOrder.setPayResult(payResult);
            } else {
                log.info("failed,resp code = " + statusCode+ ",return body = " + payResult);
                weChatPayOrder.setPayResult(payResult);
                throw new IOException("request failed");
            }
        } finally {
            // TODO: 2022/5/20 保存预订单结果
//            iWeChatPayOrderService.saveOrUpdate(weChatPayOrder);
            response.close();
            httpClient.close();
        }
        return Result.OK(payResult);
    }

    @Override
    public Result<?> sendTemplateMsgToWxUser(JSONObject jsonObject) throws IOException {
        String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s";
        Result<?> accessTokenResult = getNormalAccessToken();
        boolean success = accessTokenResult.isSuccess();
        if(!success){
            return accessTokenResult;
        }
        String accessToken = accessTokenResult.getResult().toString();
        url = String.format(url,accessToken);
        HttpPost httpPost = new HttpPost(url);
        JSONObject body = new JSONObject();
        body.put("touser", jsonObject.getString("openid"));
        body.put("template_id", passRecordNoticeTemplateId);
        body.put("url", passRecordPageUrl + "?id=" + jsonObject.getString("id"));
        JSONObject templateData = new JSONObject();
        // 您的孩子已经到达学校
        templateData.put("msg1", jsonObject.getString("msg1"));
        // 到校时间：07：35    2021-11-02
        templateData.put("msg2", jsonObject.getString("msg2"));
        // 通行记录ID
        templateData.put("passRecordId", jsonObject.getString("passRecordId"));
        StringEntity entity = new StringEntity(body.toJSONString(),"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        //完成签名并执行请求
        CloseableHttpResponse response = httpClient.execute(httpPost);
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("sendTemplateMsgToWxUser success, return body = " + EntityUtils.toString(response.getEntity()));
            } else if (statusCode == 204) {
                log.info("sendTemplateMsgToWxUser success");
            } else {
                log.info("sendTemplateMsgToWxUser failed, resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
                throw new IOException("request failed");
            }
        } finally {
            if (response != null) {
                response.close();
            }
            if (httpClient != null) {
                httpClient.close();
            }
        }
        return Result.OK();
    }

    /**
     * 根据商户订单号查询订单
     * @param outTradeNo
     * @return
     */
    @Override
    public Result<?> queryOrderByOutTradeNo(String outTradeNo) throws Exception {
        CloseableHttpClient httpClient = setupHttpClient();
        //请求URL
        URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/"+outTradeNo);
        uriBuilder.setParameter("mchid", mchId);
        //完成签名并执行请求
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader("Accept", "application/json");
        CloseableHttpResponse response = httpClient.execute(httpGet);
        String result = EntityUtils.toString(response.getEntity());
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("success,return body = " + result);
            } else if (statusCode == 204) {
                log.info("success");
            } else {
                log.error("failed,resp code = " + statusCode+ ",return body = " + result);
                return Result.error(result);
            }
        } finally {
            response.close();
            httpClient.close();
        }
        //解析数据并更新入库
        NotifyBody notifyBody = JSONObject.parseObject(result, NotifyBody.class);
        if(null != notifyBody){
            updateOrderStatus(outTradeNo, notifyBody);
        }
        return Result.OK(result);
    }

    /**
     * 更新预支付订单表状态
     * @param outTradeNo
     * @param notifyBody
     */
    private void updateOrderStatus(String outTradeNo, NotifyBody notifyBody) {
        WeChatPayOrder weChatPayOrder = new WeChatPayOrder();
        weChatPayOrder.setId(outTradeNo);
        weChatPayOrder.setTransactionId(notifyBody.getTransaction_id());
        String tradeState = notifyBody.getTrade_state();
        if (StringUtils.equalsIgnoreCase("SUCCESS", tradeState)) {
            weChatPayOrder.setOrderStatus(Constants.WECHAT_ORDER_STATUS_PAY);
        }
        if (StringUtils.equalsIgnoreCase("CLOSED", tradeState)) {
            weChatPayOrder.setOrderStatus(Constants.WECHAT_ORDER_STATUS_CLOSE);
        }
        weChatPayOrder.setUpdateTime(new Date());
        // TODO: 2022/5/20 更新订单表状态
//        iWeChatPayOrderService.updateById(weChatPayOrder);
    }

    /**
     * 根据微信支付订单号查询订单
     * @param transactionId
     * @return
     * @throws IOException
     * @throws URISyntaxException
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<?> queryOrderByTransactionId(String transactionId) throws Exception {
        CloseableHttpClient httpClient = setupHttpClient();
        //请求URL
        URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/id/"+transactionId);
        uriBuilder.setParameter("mchid", mchId);

        //完成签名并执行请求
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader("Accept", "application/json");
        CloseableHttpResponse response = httpClient.execute(httpGet);
        String result = EntityUtils.toString(response.getEntity());
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("success,return body = " + result);
            } else if (statusCode == 204) {
                log.info("success");
            } else {
                log.error("failed,resp code = " + statusCode+ ",return body = " + result);
                return Result.error(result);
            }
        } finally {
            response.close();
            httpClient.close();
        }
        //解析数据并更新入库
        NotifyBody notifyBody = JSONObject.parseObject(result, NotifyBody.class);
        if(null != notifyBody){
            String outTradeNo = notifyBody.getOut_trade_no();
            updateOrderStatus(outTradeNo, notifyBody);
        }

        return Result.OK(result);
    }

    /**
     * 关闭订单
     * @param outTradeNo
     * @return
     * @throws IOException
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<?> closeOrder(String outTradeNo) throws Exception {
        WeChatPayOrder weChatPayOrder = new WeChatPayOrder();
        weChatPayOrder.setId(outTradeNo);
        weChatPayOrder.setRemark("关闭订单");
        CloseableHttpClient httpClient = setupHttpClient();
        //请求URL
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/"+outTradeNo+"/close");
        //请求body参数
        String reqdata ="{\"mchid\": \""+mchId+"\"}";

        StringEntity entity = new StringEntity(reqdata,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = httpClient.execute(httpPost);
        String result = EntityUtils.toString(response.getEntity());
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("success,return body = " + result);
                weChatPayOrder.setOrderStatus(Constants.WECHAT_ORDER_STATUS_CLOSE);
            } else if (statusCode == 204) {
                log.info("success");
                weChatPayOrder.setOrderStatus(Constants.WECHAT_ORDER_STATUS_CLOSE);
            } else {
                log.error("failed,resp code = " + statusCode+ ",return body = " + result);
                return Result.error(result);
            }
        } finally {
            // TODO: 2022/5/20 更新订单装填
//            iWeChatPayOrderService.updateById(weChatPayOrder);
            response.close();
            httpClient.close();
        }
        return Result.OK(result);
    }

    /**
     *订单支付结果通知
     * @param request
     * @param response
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String,String> notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        Map<String,String> map = Maps.newHashMap();
        map.put("code","SUCCESS");
        map.put("message","成功");
        // 读取请求报文
        String body = readNotifyMessage(request).toString();
        //幂等校验
        String md5Encode = Md5Utils.encrypt(body);
        //微信通知总计时长为 24h4m
        Boolean isAbsent = stringRedisTemplate.opsForValue().setIfAbsent(MD5ENCODE + md5Encode, "md5Encode", 1444, TimeUnit.MINUTES);
        //存在则直接返回
        if(!isAbsent){
            return map;
        }

        //微信支付平台证书序列号
        String wechatPaySerial = request.getHeader(WechatPayHttpHeaders.WECHAT_PAY_SERIAL);
        String timestamp  = request.getHeader(WechatPayHttpHeaders.WECHAT_PAY_TIMESTAMP);
        String nonce = request.getHeader(WechatPayHttpHeaders.WECHAT_PAY_NONCE);
        String signature = request.getHeader(WechatPayHttpHeaders.WECHAT_PAY_SIGNATURE);

        //验签
        //privateKeyPath为私钥存储路径
        String privateKeyPath = "";
        String privateKey = new String(Files.readAllBytes(Paths.get(privateKeyPath)), "utf-8");
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);
        // 获取证书管理器实例
        Verifier verifier = getVerifier(merchantPrivateKey);

        //解密支付回调数据
        // 构建request，传入必要参数
        NotificationRequest notificationRequest = new NotificationRequest.Builder().withSerialNumber(wechatPaySerial)
                .withNonce(nonce)
                .withTimestamp(timestamp)
                .withSignature(signature)
                .withBody(body)
                .build();
        NotificationHandler handler = new NotificationHandler(verifier, apiV3Key.getBytes(StandardCharsets.UTF_8));
        // 验签和解析请求体
        Notification notification = handler.parse(notificationRequest);
        String notificationStr = notification.toString();
        log.info("微信支付回调参数", notificationStr);

        //解析请求体并保存数据,如果处理过慢可采用异步处理
        NotifyBody notifyBody = decryptNotifyMessage(notification);
        saveNotifyParams(notificationStr, notifyBody);
        return map;
    }

    /**
     * 保存回调参数
     * @param notificationStr
     * @param notifyBody
     */
    private void saveNotifyParams(String notificationStr, NotifyBody notifyBody) {
        if(null != notifyBody){
            WeChatPayNotify weChatPayNotify = new WeChatPayNotify();
            weChatPayNotify.setAppId(notifyBody.getAppid());
            weChatPayNotify.setMchId(notifyBody.getMchid());
            String outTradeNo = notifyBody.getOut_trade_no();
            weChatPayNotify.setOutTradeNo(outTradeNo);
            String transactionId = notifyBody.getTransaction_id();
            weChatPayNotify.setId(transactionId);
            weChatPayNotify.setTradeType(notifyBody.getTrade_type());
            String tradeState = notifyBody.getTrade_state();
            weChatPayNotify.setTradeState(tradeState);
            weChatPayNotify.setSuccessTime(notifyBody.getSuccess_time());
            weChatPayNotify.setOriginalData(notificationStr);
            weChatPayNotify.setResourceData(notifyBody);
            weChatPayNotify.setResourceData(notifyBody);
            Date date = new Date();
            weChatPayNotify.setCreateTime(date);
            weChatPayNotify.setUpdateTime(date);
            // TODO: 2022/5/20 保存回调结果
//            iWeChatPayNotifyService.saveOrUpdate(weChatPayNotify);
            //已支付则修改微信支付预订单表状态
            if(StringUtils.equalsIgnoreCase("SUCCESS",tradeState)){
                WeChatPayOrder weChatPayOrder = new WeChatPayOrder();
                weChatPayOrder.setId(outTradeNo);
                weChatPayOrder.setTransactionId(transactionId);
                weChatPayOrder.setOrderStatus(Constants.WECHAT_ORDER_STATUS_PAY);
                weChatPayOrder.setUpdateTime(new Date());
                // TODO: 2022/5/20 更新订单状态
//                iWeChatPayOrderService.updateById(weChatPayOrder);
            }
        }
    }

    /**
     * 解密支付回调数据
     *
     * @param notification 请求报文
     * @return NotifyBody 微信返回数据实体类
     */
    public NotifyBody decryptNotifyMessage(Notification notification) throws GeneralSecurityException {
        //获取加密数据
        Notification.Resource resource = notification.getResource();
        AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
        String s = aesUtil.decryptToString(resource.getAssociatedData().getBytes(StandardCharsets.UTF_8),
                resource.getNonce().getBytes(StandardCharsets.UTF_8),
                resource.getCiphertext());
        return JSONObject.parseObject(s, NotifyBody.class);
    }

    /**
     * 读取支付回调数据的报文
     *
     * @param request 请求体
     * @return StringBuilder 报文信息json格式
     */
    public StringBuilder readNotifyMessage(HttpServletRequest request) {
        BufferedReader br = null;
        StringBuilder sb = new StringBuilder();
        try {
            br = request.getReader();
            String str;
            while ((str = br.readLine()) != null) {
                sb.append(str);
            }
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != br) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb;
    }

    /**
     * 初始化httpClient
     * @return
     * @throws IOException
     */
    public CloseableHttpClient setupHttpClient() throws IOException, GeneralSecurityException, HttpCodeException, NotFoundException {
        //获取商户私钥
        //fileName为私钥存储路径
        String privateKeyPath = "";
        String privateKey = new String(Files.readAllBytes(Paths.get(privateKeyPath)), "utf-8");
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);
        //获取微信支付平台证书
        // 加载平台证书（mchId：商户号,mchSerialNo：商户证书序列号,apiV3Key：V3秘钥）
        Verifier verifier = getVerifier(merchantPrivateKey);
        //官方jar包中未找到这个类ScheduledUpdateCertificatesVerifier,然而AutoUpdateCertificatesVerifier已经弃用
        /*ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
                new WechatPay2Credentials(mchId,
                        new PrivateKeySigner(mchSerialNo,
                                merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));*/

        CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier))
                .build();
        return httpClient;
    }

    /**
     * 获取微信支付平台证书
     * @param merchantPrivateKey
     * @return
     * @throws IOException
     * @throws GeneralSecurityException
     * @throws HttpCodeException
     * @throws NotFoundException
     */
    private Verifier getVerifier(PrivateKey merchantPrivateKey) throws IOException, GeneralSecurityException, HttpCodeException, NotFoundException {
        CertificatesManager certificatesManager = CertificatesManager.getInstance();
        // 向证书管理器增加需要自动更新平台证书的商户信息
        certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
                new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
        // 从证书管理器中获取verifier验签器
        // CertificatesManager.getVerifier(mchId) 得到的验签器会替代默认的验签器，它会定时下载和更新商户对应的微信支付平台证书
        return certificatesManager.getVerifier(mchId);
    }

    /**
     * 根据code获取accessToken
     * @param code
     * @return
     */
    private JSONObject getAccessTokenByCode (String code){
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
        url = String.format(url,appId,secret,code);
        String result = HttpUtil.get(url);
        if(StringUtils.isBlank(result)){
            return null;
        }
        JSONObject jsonObject = JSON.parseObject(result);
        String openId = jsonObject.getString("openid");
        try {
            stringRedisTemplate.opsForValue().set(WECHAT_WEB_ACCESSTOKEN + openId,jsonObject.toString(),30, TimeUnit.DAYS);
        }catch (Exception e){
            log.error("保存微信网页授权access_token到redis失败",e);
            return jsonObject;
        }
        return jsonObject;
    }

    /**
     * 获取用户信息
     * @param jsonObject
     * @return
     */
    private WeChatWebUserInfo getWeChatInfoByAccessToken(JSONObject jsonObject){
        String url = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN";
        String accessToken = jsonObject.getString("access_token");
        String openId = jsonObject.getString("openid");
        url = String.format(url,accessToken,openId);
        String result = HttpUtil.get(url);
        if(StringUtils.isBlank(result)){
            return null;
        }
        WeChatWebUserInfo weChatWebUserInfo = JSONObject.parseObject(result,WeChatWebUserInfo.class);
        try {
            stringRedisTemplate.opsForValue().set(WECHAT_WEB_USERINFO + openId,result,30, TimeUnit.DAYS);
        }catch (Exception e){
            log.error("保存微信网页授权用户信息到redis失败",e);
        }
        return weChatWebUserInfo;
    }

}
